#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

using Weazel.Gezel.Model;
using Weazel.Gezel.Model.Errors;

namespace Weazel.Gezel.Parser
{
  public static class ParseSystem
  {
    public static readonly Set<Token> First = new Set<Token>(Token.System);

    public static void Parse(Set<Token> follow, Tokenizer t, Model.Model parent)
    {
      Set<Token> current = new Set<Token>(
        // Token.System,
        Token.Identifier,
        Token.LeftBrace,
        Token.RightBrace
      );

      current = current.Union(ParseSystemDefintion.First);
			
			Position startPosition = t.CurrentPosition;

      t.Accept(Token.System, follow.Union(current));

      string name = t.CurrentToken == Token.Identifier ? t.CurrentString : null;

      system sys = new system(parent, name, startPosition, null);

      current.Remove(Token.Identifier);
      t.Accept(Token.Identifier, follow.Union(current));

      current.Remove(Token.LeftBrace);
      t.Accept(Token.LeftBrace, follow.Union(current));

      current.Remove(ParseSystemDefintion.First);
      ParseSystemDefintion.Parse(follow.Union(current), t, sys);

			sys.EndPosition = t.CurrentPosition;

      current.Remove(Token.RightBrace);
      t.Accept(Token.RightBrace, follow.Union(current));

      if (name != null)
        parent.Systems.Add(name, sys);
    }
  }

  public static class ParseSystemDefintion
  {
    public static readonly Set<Token> First =
      new Set<Token>().Union(ParseSystemDatapath.First).Union(ParseSystemStimulus.First);

    public static void Parse(Set<Token> follow, Tokenizer t, system sys)
    {
      Set<Token> current = new Set<Token>();

      current = current.Union(ParseSystemDatapath.First);
      current = current.Union(ParseSystemStimulus.First);

      bool done = false;

      do
      {
        t.Check(follow.Union(current));

        if (ParseSystemDatapath.First.Contains(t.CurrentToken))
        {        
          ParseSystemDatapath.Parse(follow.Union(current), t, sys);
        }
        else if (ParseSystemStimulus.First.Contains(t.CurrentToken))
        {          
          ParseSystemStimulus.Parse(follow.Union(current), t, sys);
        }
        else
        {
          done = true;
        }

      } while (!done);
    }
  }

  /// <summary>
  /// The parse method in this class parses a comma seperated identifer list
  /// </summary>
	public static class ParseSystemDatapathBindingList
	{
		public static readonly Set<Token> First = new Set<Token>(Token.Identifier);

		public static List<string> Parse(Set<Token> follow, Tokenizer t, system sys, string currentDp)
		{
			List<string> result = new List<string>();

			Set<Token> current = new Set<Token>(
				Token.Identifier,
				Token.Comma,
				Token.Dot
			);

			bool done = false;

			int declarationPosition = 0;

			do
			{
				string first = t.CurrentToken == Token.Identifier ? 
					t.CurrentString : null;

				string second = null;
				
				t.Accept(Token.Identifier, follow.Union(current));

				t.Check(follow.Union(current));
				if (t.CurrentToken == Token.Comma)
				{
					t.Accept(Token.Comma, follow.Union(current));
				}
				else if(t.CurrentToken == Token.Dot)
				{
					t.Accept(Token.Dot, follow.Union(current));

					second = t.CurrentToken == Token.Identifier ?
						t.CurrentString : null;

					t.Accept(Token.Identifier, follow.Union(current));

					t.Check(follow.Union(current));
					if (t.CurrentToken == Token.Comma)
						t.Accept(Token.Comma, follow.Union(current));
					else
						done = true;
				}
				else
				{
					done = true;
				}

				if (first != null && second != null)
				{					
					system.DatapathConnectionInfo connection =
						new system.DatapathConnectionInfo(sys);

					system.DatapathConnectorInfo connector1 =
						new system.DatapathConnectorInfo(currentDp, declarationPosition++);

					system.DatapathConnectorInfo connector2 =
						new system.DatapathConnectorInfo(first, second);

					connection.Connectors.Add(connector1);
					connection.Connectors.Add(connector2);

					// using auto generated name, should not cause duplication error
					sys.PortConnections.Add(connection.LocalName, connection);
				}
				else if (first != null)
				{	
					// first is name of connecting signal
					system.DatapathConnectionInfo connection =
						sys.PortConnections.ContainsKey(first) ?
							sys.PortConnections[first]
						: new system.DatapathConnectionInfo(sys, first);

					system.DatapathConnectorInfo connector =
						new system.DatapathConnectorInfo(currentDp, declarationPosition++);

					connection.Connectors.Add(connector);

					if (!sys.PortConnections.ContainsKey(first))
						sys.PortConnections.Add(connection.LocalName, connection);
				}
				
			}
			while (!done);

			return result;
		}
	}


  /// <summary>
  /// SystemDatapath : 
  ///     SystemDatapathId LEFT_PARENTHESIS SystemDatapathPinList RIGHT_PARENTHESIS SEMICOLON
  ///   | SystemDatapathId SEMICOLON
	///   | SystemDatapathId DOT DatapathPortId EQUALS SystemDatapathId DOT DatapathPortId 
	///		| SystemDatapathId DOT DatapathPortId EQUALS PinName
  /// </summary>
    public static class ParseSystemDatapath
    {
      public static readonly Set<Token> First = new Set<Token>(Token.Identifier);

      public static void Parse(Set<Token> follow, Tokenizer t, system sys)
      {
        Set<Token> current = new Set<Token>(
          // Token.Identifier,
					Token.Dot,
					Token.Assign,
					Token.Identifier,
          Token.LeftParenthesis,
          Token.RightParenthesis,
          Token.Semicolon
        );

        current = current.Union(ParseList.First);

        string first = t.CurrentToken == Token.Identifier ? t.CurrentString : null;

        t.Accept(Token.Identifier, follow.Union(current));

        t.Check(follow.Union(current));

				if (t.CurrentToken == Token.Dot)
				{					
					t.Accept(Token.Dot, follow.Union(current));

					string second = t.CurrentToken == Token.Identifier ? t.CurrentString : null;

					t.Accept(Token.Identifier, follow.Union(Token.Assign, Token.Identifier, Token.Dot, Token.Semicolon));

					t.Accept(Token.Assign, follow.Union(Token.Identifier, Token.Dot, Token.Semicolon));

					string third = t.CurrentToken == Token.Identifier ? t.CurrentString : null;

					t.Accept(Token.Identifier, follow.Union(Token.Identifier, Token.Dot, Token.Semicolon));

					t.Check(follow.Union(Token.Dot, Token.Identifier, Token.Semicolon));

					if (t.CurrentToken == Token.Semicolon)
					{
						t.Accept(Token.Semicolon, follow);

						if (first != null && second != null && third != null)
						{
							system.DatapathConnectionInfo connection =
									sys.PortConnections.ContainsKey(third) ?
										sys.PortConnections[third]
									: new system.DatapathConnectionInfo(sys);

							system.DatapathConnectorInfo connector =
								new system.DatapathConnectorInfo(first, second);

							connection.Connectors.Add(connector);

							if (!sys.PortConnections.ContainsKey(third))
								sys.PortConnections.Add(connection.LocalName, connection);
						}
					}
					else if (t.CurrentToken == Token.Dot)
					{
						t.Accept(Token.Dot, follow.Union(Token.Identifier, Token.Semicolon));

						string fourth = t.CurrentToken == Token.Identifier ? t.CurrentString : null;

						t.Accept(Token.Identifier, follow.Union(Token.Semicolon));

						t.Accept(Token.Semicolon, follow);

						if (first != null && second != null && third != null && fourth != null)
						{
							system.DatapathConnectionInfo connection =
								new system.DatapathConnectionInfo(sys);

							system.DatapathConnectorInfo connector1 =
								new system.DatapathConnectorInfo(first, second);

							system.DatapathConnectorInfo connector2 =
								new system.DatapathConnectorInfo(third, fourth);

							connection.Connectors.Add(connector1);
							connection.Connectors.Add(connector2);

							// using auto generated name, no need to check for duplication
							sys.PortConnections.Add(connection.LocalName, connection);
						}
					}
					else
					{
						t.Errors.Add(
							new UnexpectedTokenParseError(
								t.CurrentPosition,
								t.CurrentToken,
								follow.Union(Token.Dot, Token.Identifier, Token.Semicolon)
							)
						);
					}
																				
				}
        else if (t.CurrentToken == Token.LeftParenthesis)
        {
					current.Remove(Token.Dot, Token.Assign, Token.LeftParenthesis);
          t.Accept(Token.LeftParenthesis, follow.Union(current));

          current.Remove(ParseList.First);
					ParseSystemDatapathBindingList.Parse(follow.Union(current), t, sys, first);
          
          current.Remove(Token.RightParenthesis);
          t.Accept(Token.RightParenthesis, follow.Union(current));

          current.Remove(Token.Semicolon);
          t.Accept(Token.Semicolon, follow); // current is empty 

					//if (first != null)
					//  sys.DatapathRefs.Add(first, pinList.ToArray());
        }
        else if (t.CurrentToken == Token.Semicolon)
        {
          // datapath with empty pinlist
          t.Accept(Token.Semicolon, follow);

					if (first != null)
						sys.UnconnectedDatapaths.Add(first);           
        }
        else
        {
          t.Errors.Add(
            new UnexpectedTokenParseError(
              t.CurrentPosition,
              t.CurrentToken,
              follow.Union(current)
            )
          );
        }
      }
    }

    public static class ParseSystemStimulus
    {
      public static readonly Set<Token> First = new Set<Token>(Token.Stimulus);

      public static void Parse(Set<Token> follow, Tokenizer t, system sys)
      {
        Set<Token> current = new Set<Token>(
          // Token.Stimulus,
          Token.LeftParenthesis,
          Token.Identifier,
          Token.Comma,
          Token.Str,
          Token.RightParenthesis,
          Token.Semicolon
        );
				
				Position startPosition = t.CurrentPosition;

        t.Accept(Token.Stimulus, follow.Union(current));

        current.Remove(Token.LeftParenthesis);
        t.Accept(Token.LeftParenthesis, follow.Union(current));

        string signal = (t.CurrentToken == Token.Identifier) ? t.CurrentString : null;

        current.Remove(Token.Identifier);
        t.Accept(Token.Identifier, follow.Union(current));

        current.Remove(Token.Comma);
        t.Accept(Token.Comma, follow.Union(current));

        string file = (t.CurrentToken == Token.Str) ? t.CurrentString : null;

        current.Remove(Token.Str);
        t.Accept(Token.Str, follow.Union(current));

        current.Remove(Token.RightParenthesis);
        t.Accept(Token.RightParenthesis, follow.Union(current));

				Position endPosition = t.CurrentEndPosition;

        // no need to remove 'semicolon' (last element in set) ->
        // just pass on 'follow' instead
        t.Accept(Token.Semicolon, follow);

        if (file != null && signal != null)
        {
          string name = "stimulus" + sys.Stimuli.Count.ToString();
          Stimulus s = new Stimulus(sys, name, file, signal, startPosition, endPosition);
          sys.Stimuli.Add(name, s);
        }
      }
    }
  }
